package com.atguigu.gmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.product.model.SkuInfo;
import com.atguigu.gmall.product.service.TestService;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;
   /*@Override
   public synchronized void testLock() {
      // 查询Redis中的num值
      String value = (String)this.redisTemplate.opsForValue().get("num");
      // 没有该值return
      if (StringUtils.isBlank(value)){
         return ;
      }
      // 有值就转成成int
      int num = Integer.parseInt(value);
      // 把Redis中的num值+1
      this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
   }*/

    private final String key = "lock";
    private final String value = "lock";

    /**
     * 使用SpringDataRedis完成分布式锁-采用setnx命令同时设置锁过期时间
     * 问题：缺乏原子性：如果在setnx和expire之间出现异常，锁也无法释放
     */
    /*@Override
    public void testLock() {
        //1.尝试获取分布式锁
        //Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value); //setnx命令+expire 当key不存在，才能写成功
        String lockVal = UUID.randomUUID().toString();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, lockVal, 5, TimeUnit.SECONDS); //set key val nx ex 5
        if (flag) {
            try {
                //redisTemplate.expire(key, Duration.ofSeconds(5));
                //2.获取锁成功后，执行业务逻辑
                // 查询Redis中的num值
                String value = (String) this.redisTemplate.opsForValue().get("num");
                // 没有该值return
                if (StringUtils.isBlank(value)) {
                    return;
                }
                // 有值就转成成int
                int num = Integer.parseInt(value);
                // 把Redis中的num值+1
                this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
            } finally {
                //3.将锁释放
                //redisTemplate.delete(key);
                //删除锁前，判断该锁是否为当前线程的锁
                //3.1 先获取Redis中锁值 跟当前线程锁值比较
                //String redisLockVal = redisTemplate.opsForValue().get(key);
                //3.2 如果两个锁值相同再去释放锁
                //if (lockVal.equals(redisLockVal)) {
                //    redisTemplate.delete(key);
                //}
                //3.3 采用lua脚本保证判断删除原子操作
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(script);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(key), lockVal);
            }
        } else {
            try {
                //获取锁失败，尝试自旋
                Thread.sleep(500);
                this.testLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }*/

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void testLock() throws InterruptedException {
        //1.创建锁对象
        RLock lock = redissonClient.getLock("lock");

        //2.获取锁
        //lock.lock(); //一直阻塞知道获取锁成功为止，唯一底层包含“看门狗”机制方法-锁过期时间默认为：30s
        //boolean flag = lock.tryLock(1, TimeUnit.SECONDS); //指定获取锁最大等待时间 锁过期时间默认为：30s
        boolean flag = lock.tryLock(1, 5, TimeUnit.SECONDS); //指定获取锁最大等待时间，指定锁过期时间
        if (flag) {
            try {
                // 查询Redis中的num值
                String value = (String) this.redisTemplate.opsForValue().get("num");
                // 没有该值return
                if (StringUtils.isBlank(value)) {
                    return;
                }
                this.check();
                // 有值就转成成int
                int num = Integer.parseInt(value);
                // 把Redis中的num值+1
                this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
            } finally {
                lock.unlock();
            }
        } else {
            try {
                System.out.println("获取锁失败，尝试自旋");
                TimeUnit.MILLISECONDS.sleep(500);
                this.testLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }


    private void check() {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        System.out.println("测试可重入");
        lock.unlock();
    }

    @Value("${server.port}")
    private String port;

    /**
     * 读取数据方法
     *
     * @return
     */
    @Override
    public String read() {
        System.out.println("read当前节点被调用：" + port);
        //1.创建读写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock");
        //2.获取读锁对象
        RLock lock = rwlock.readLock();
        //3.获取读锁
        lock.lock(5, TimeUnit.SECONDS);

        //优先从缓存中获取数据
        String data = redisTemplate.opsForValue().get("data");
        if (StringUtils.isNotBlank(data)) {
            //TODO 模拟查询数据库
            data = "dbData";
            //将查询结果放入缓存
            redisTemplate.opsForValue().set("daa", data);
        }

        //todo 故意不释放读锁  - 5s后自动释放
        lock.unlock();
        return data;
    }


    /**
     * 写数据方法
     */
    @Override
    public void write() {
        System.out.println("write当前节点被调用：" + port);
        //1.创建读写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock");
        //2.获取写锁对象
        RLock lock = rwlock.writeLock();
        //3.获取写锁
        lock.lock(5, TimeUnit.SECONDS);

        //删除缓存
        redisTemplate.delete("data");

        //模拟修改数据库
        String data = "writeData";

        //todo 故意释放写锁  -- 5s后自动释放
        lock.unlock();
    }

    @Override
    @Async("customAsyncThreadPool")  //异步任务 该方法执行提交到springboot默认线程池执行
    public String testAsync(String data) {
        System.out.println(Thread.currentThread().getName()+"\ttestAsync被调用了");
        return "return:" + data;
    }


    /**
     * 查询放入缓存商品信息
     *
     * @param skuId
     * @return
     */
    public SkuInfo getSkuInfo(Long skuId) {
        //1.先查询Redis获取商品信息
        String skuInfoStr = redisTemplate.opsForValue().get("sku:" + skuId + ":info");
        if (StringUtils.isNotBlank(skuInfoStr)) {
            return JSON.parseObject(skuInfoStr, SkuInfo.class);
        } else {
            //2.如未命中缓存，避免缓存击穿，采用分布式锁
            //2.1 获取分布式锁
            Boolean flag = true;//todo 获取分布式锁：
            if (flag) {
                //执行查询数据库操作，将查库的结果放入缓存
            }
        }
        return null;
    }


}
